$db, 'log_user', User::newFromName( $params['user'], false )
);
$this->addWhere( $q['conds'] );
+
+ // T71222: MariaDB's optimizer, at least 10.1.37 and .38, likes to choose a wildly bad plan for
+ // some reason for this code path. Tell it not to use the wrong index it wants to pick.
+ $this->addOption( 'IGNORE INDEX', [ 'logging' => [ 'times' ] ] );
}
$title = $params['title'];
$ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
// Update page_touched (skipping pages already touched since the root job).
// Check $wgUpdateRowsPerQuery for sanity; batch jobs are sized by that already.
- foreach ( array_chunk( $pageIds, $wgUpdateRowsPerQuery ) as $batch ) {
- $factory->commitAndWaitForReplication( __METHOD__, $ticket );
-
+ $batches = array_chunk( $pageIds, $wgUpdateRowsPerQuery );
+ foreach ( $batches as $batch ) {
$dbw->update( 'page',
[ 'page_touched' => $dbw->timestamp( $touchTimestamp ) ],
[ 'page_id' => $batch,
],
__METHOD__
);
+ if ( count( $batches ) > 1 ) {
+ $factory->commitAndWaitForReplication( __METHOD__, $ticket );
+ }
}
// Get the list of affected pages (races only mean something else did the purge)
$titleArray = TitleArray::newFromResult( $dbw->select(
$batchSize = $services->getMainConfig()->get( 'UpdateRowsPerQuery' );
$ticket = $lbFactory->getEmptyTransactionTicket( $fname );
- foreach ( array_chunk( $ids, $batchSize ) as $idBatch ) {
+ $idBatches = array_chunk( $ids, $batchSize );
+ foreach ( $idBatches as $idBatch ) {
$dbw->update(
'page',
[ 'page_touched' => $now ],
],
$fname
);
- $lbFactory->commitAndWaitForReplication( $fname, $ticket );
+ if ( count( $idBatches ) > 1 ) {
+ $lbFactory->commitAndWaitForReplication( $fname, $ticket );
+ }
}
}
) );
* @param string $key Cache key
* @param mixed $value
* @param int $ttl Seconds to live. Special values are:
- * - WANObjectCache::TTL_INDEFINITE: Cache forever
+ * - WANObjectCache::TTL_INDEFINITE: Cache forever (default)
* @param array $opts Options map:
* - lag : Seconds of replica DB lag. Typically, this is either the replica DB lag
* before the data was read or, if applicable, the replica DB lag before
* @note Options added in 1.28: staleTTL
* @return bool Success
*/
- final public function set( $key, $value, $ttl = 0, array $opts = [] ) {
+ final public function set( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) {
$now = $this->getCurrentTime();
$lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
$staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
return $this->__call( __FUNCTION__, func_get_args() );
}
+ public function addIdentifierQuotes( $s ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
public function buildLike() {
return $this->__call( __FUNCTION__, func_get_args() );
}
}
/**
+ * Error out if the DB is not in a valid state for a query via query()
+ *
* @param string $sql
* @param string $fname
* @throws DBTransactionStateError
*/
private function assertTransactionStatus( $sql, $fname ) {
- if ( $this->getQueryVerb( $sql ) === 'ROLLBACK' ) { // transaction/savepoint
+ $verb = $this->getQueryVerb( $sql );
+ if ( $verb === 'USE' ) {
+ throw new DBUnexpectedError( $this, "Got USE query; use selectDomain() instead." );
+ }
+
+ if ( $verb === 'ROLLBACK' ) { // transaction/savepoint
return;
}
}
}
- /**
- * Quotes an identifier using `backticks` or "double quotes" depending on the database type.
- * MySQL uses `backticks` while basically everything else uses double quotes.
- * Since MySQL is the odd one out here the double quotes are our generic
- * and we implement backticks in DatabaseMysqlBase.
- *
- * @param string $s
- * @return string
- */
public function addIdentifierQuotes( $s ) {
return '"' . str_replace( '"', '""', $s ) . '"';
}
}
protected function doSelectDomain( DatabaseDomain $domain ) {
- $encDatabase = $this->addIdentifierQuotes( $domain->getDatabase() );
- $this->query( "USE $encDatabase" );
+ if ( $domain->getSchema() !== null ) {
+ throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
+ }
+
+ $database = $domain->getDatabase();
+ if ( $database !== $this->getDBname() ) {
+ $encDatabase = $this->addIdentifierQuotes( $database );
+ $res = $this->doQuery( "USE $encDatabase" );
+ if ( !$res ) {
+ throw new DBExpectedError( $this, "Could not select database '$database'." );
+ }
+ }
// Update that domain fields on success (no exception thrown)
$this->currentDomain = $domain;
}
function doSelectDomain( DatabaseDomain $domain ) {
- $conn = $this->getBindingHandle();
-
if ( $domain->getSchema() !== null ) {
throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
}
$database = $domain->getDatabase();
- if ( !$conn->select_db( $database ) ) {
- throw new DBExpectedError( $this, "Could not select database '$database'." );
+ if ( $database !== $this->getDBname() ) {
+ $conn = $this->getBindingHandle();
+ if ( !$conn->select_db( $database ) ) {
+ throw new DBExpectedError( $this, "Could not select database '$database'." );
+ }
}
// Update that domain fields on success (no exception thrown)
*/
public function addQuotes( $s );
+ /**
+ * Quotes an identifier, in order to make user controlled input safe
+ *
+ * Depending on the database this will either be `backticks` or "double quotes"
+ *
+ * @param string $s
+ * @return string
+ * @since 1.33
+ */
+ public function addIdentifierQuotes( $s );
+
/**
* LIKE statement wrapper, receives a variable-length argument list with
* parts of pattern to match containing either string literals that will be
// mw.loader.implement will use globalEval if scripts is a string.
// Minify manually here, because general response minification is
// not effective due it being a string literal, not a function.
- if ( !self::inDebugMode() ) {
+ if ( !$context->getDebug() ) {
$scripts = self::filter( 'minify-js', $scripts ); // T107377
}
} else {
'modules' => self::makePackedModulesString( $modules ),
'lang' => $lang,
'skin' => $skin,
- 'debug' => $debug ? 'true' : 'false',
];
+ if ( $debug === true ) {
+ $query['debug'] = 'true';
+ }
if ( $user !== null ) {
$query['user'] = $user;
}
// Various parameters
$this->user = $request->getRawVal( 'user' );
- $this->debug = $request->getFuzzyBool(
- 'debug',
- $this->getConfig()->get( 'ResourceLoaderDebug' )
- );
+ $this->debug = $request->getRawVal( 'debug' ) === 'true';
$this->only = $request->getRawVal( 'only', null );
$this->version = $request->getRawVal( 'version', null );
$this->raw = $request->getFuzzyBool( 'raw' );
}
ul {
- list-style-type: square;
margin: 0.3em 0 0 1.6em;
padding: 0;
}
$options = [ 'lang' => $options ];
}
$options += [
+ 'debug' => 'true',
'lang' => 'en',
'dir' => 'ltr',
'skin' => 'vector',
];
$resourceLoader = $rl ?: new ResourceLoader();
$request = new FauxRequest( [
+ 'debug' => $options['debug'],
'lang' => $options['lang'],
'modules' => $options['modules'],
'only' => $options['only'],
[
[ 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS ],
"<script nonce=\"secret\">(window.RLQ=window.RLQ||[]).push(function(){"
- . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.foo\u0026only=scripts\u0026skin=fallback");'
+ . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?lang=en\u0026modules=test.foo\u0026only=scripts\u0026skin=fallback");'
. "});</script>"
],
// Multiple only=styles load
[
[ [ 'test.baz', 'test.foo', 'test.bar' ], ResourceLoaderModule::TYPE_STYLES ],
- '<link rel="stylesheet" href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.bar%2Cbaz%2Cfoo&only=styles&skin=fallback"/>'
+ '<link rel="stylesheet" href="http://127.0.0.1:8080/w/load.php?lang=en&modules=test.bar%2Cbaz%2Cfoo&only=styles&skin=fallback"/>'
],
// Private embed (only=scripts)
[
// noscript group
[
[ 'test.noscript', ResourceLoaderModule::TYPE_STYLES ],
- '<noscript><link rel="stylesheet" href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.noscript&only=styles&skin=fallback"/></noscript>'
+ '<noscript><link rel="stylesheet" href="http://127.0.0.1:8080/w/load.php?lang=en&modules=test.noscript&only=styles&skin=fallback"/></noscript>'
],
// Load two modules in separate groups
[
[ [ 'test.group.foo', 'test.group.bar' ], ResourceLoaderModule::TYPE_COMBINED ],
"<script nonce=\"secret\">(window.RLQ=window.RLQ||[]).push(function(){"
- . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.bar\u0026skin=fallback");'
- . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.foo\u0026skin=fallback");'
+ . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?lang=en\u0026modules=test.group.bar\u0026skin=fallback");'
+ . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?lang=en\u0026modules=test.group.foo\u0026skin=fallback");'
. "});</script>"
],
];
'default logged-out' => [
'exemptStyleModules' => [ 'site' => [ 'site.styles' ] ],
'<meta name="ResourceLoaderDynamicStyles" content=""/>' . "\n" .
- '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=site.styles&only=styles&skin=fallback"/>',
+ '<link rel="stylesheet" href="/w/load.php?lang=en&modules=site.styles&only=styles&skin=fallback"/>',
],
'default logged-in' => [
'exemptStyleModules' => [ 'site' => [ 'site.styles' ], 'user' => [ 'user.styles' ] ],
'<meta name="ResourceLoaderDynamicStyles" content=""/>' . "\n" .
- '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=site.styles&only=styles&skin=fallback"/>' . "\n" .
- '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=user.styles&only=styles&skin=fallback&version=1ai9g6t"/>',
+ '<link rel="stylesheet" href="/w/load.php?lang=en&modules=site.styles&only=styles&skin=fallback"/>' . "\n" .
+ '<link rel="stylesheet" href="/w/load.php?lang=en&modules=user.styles&only=styles&skin=fallback&version=1ai9g6t"/>',
],
'custom modules' => [
'exemptStyleModules' => [
'user' => [ 'user.styles', 'example.user' ],
],
'<meta name="ResourceLoaderDynamicStyles" content=""/>' . "\n" .
- '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=example.site.a%2Cb&only=styles&skin=fallback"/>' . "\n" .
- '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=site.styles&only=styles&skin=fallback"/>' . "\n" .
- '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=example.user&only=styles&skin=fallback&version=0a56zyi"/>' . "\n" .
- '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=user.styles&only=styles&skin=fallback&version=1ai9g6t"/>',
+ '<link rel="stylesheet" href="/w/load.php?lang=en&modules=example.site.a%2Cb&only=styles&skin=fallback"/>' . "\n" .
+ '<link rel="stylesheet" href="/w/load.php?lang=en&modules=site.styles&only=styles&skin=fallback"/>' . "\n" .
+ '<link rel="stylesheet" href="/w/load.php?lang=en&modules=example.user&only=styles&skin=fallback&version=0a56zyi"/>' . "\n" .
+ '<link rel="stylesheet" href="/w/load.php?lang=en&modules=user.styles&only=styles&skin=fallback&version=1ai9g6t"/>',
],
];
// phpcs:enable
'ResourceModuleSkinStyles' => [],
'ResourceModules' => [],
'EnableJavaScriptTest' => false,
- 'ResourceLoaderDebug' => false,
'LoadScript' => '/w/load.php',
] );
return new ResourceLoaderContext(
. 'mw.loader.implement("test.private@{blankVer}",null,{"css":[]});'
. 'RLPAGEMODULES=["test"];mw.loader.load(RLPAGEMODULES);'
. '});</script>' . "\n"
- . '<link rel="stylesheet" href="/w/load.php?debug=false&lang=nl&modules=test.styles.deprecated%2Cpure&only=styles&skin=fallback"/>' . "\n"
+ . '<link rel="stylesheet" href="/w/load.php?lang=nl&modules=test.styles.deprecated%2Cpure&only=styles&skin=fallback"/>' . "\n"
. '<style>.private{}</style>' . "\n"
- . '<script async="" src="/w/load.php?debug=false&lang=nl&modules=startup&only=scripts&skin=fallback"></script>';
+ . '<script async="" src="/w/load.php?lang=nl&modules=startup&only=scripts&skin=fallback"></script>';
// phpcs:enable
$expected = self::expandVariables( $expected );
// phpcs:disable Generic.Files.LineLength
$expected = '<script>document.documentElement.className = document.documentElement.className.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );</script>' . "\n"
- . '<script async="" src="/w/load.php?debug=false&lang=nl&modules=startup&only=scripts&skin=fallback&target=example"></script>';
+ . '<script async="" src="/w/load.php?lang=nl&modules=startup&only=scripts&skin=fallback&target=example"></script>';
// phpcs:enable
$this->assertEquals( $expected, $client->getHeadHtml() );
// phpcs:disable Generic.Files.LineLength
$expected = '<script>document.documentElement.className = document.documentElement.className.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );</script>' . "\n"
- . '<script async="" src="/w/load.php?debug=false&lang=nl&modules=startup&only=scripts&safemode=1&skin=fallback"></script>';
+ . '<script async="" src="/w/load.php?lang=nl&modules=startup&only=scripts&safemode=1&skin=fallback"></script>';
// phpcs:enable
$this->assertEquals( $expected, $client->getHeadHtml() );
// phpcs:disable Generic.Files.LineLength
$expected = '<script>document.documentElement.className = document.documentElement.className.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );</script>' . "\n"
- . '<script async="" src="/w/load.php?debug=false&lang=nl&modules=startup&only=scripts&skin=fallback"></script>';
+ . '<script async="" src="/w/load.php?lang=nl&modules=startup&only=scripts&skin=fallback"></script>';
// phpcs:enable
$this->assertEquals( $expected, $client->getHeadHtml() );
'context' => [],
'modules' => [ 'test.unknown' ],
'only' => ResourceLoaderModule::TYPE_STYLES,
+ 'extra' => [],
'output' => '',
],
[
'context' => [],
'modules' => [ 'test.styles.private' ],
'only' => ResourceLoaderModule::TYPE_STYLES,
+ 'extra' => [],
'output' => '<style>.private{}</style>',
],
[
'context' => [],
'modules' => [ 'test.private' ],
'only' => ResourceLoaderModule::TYPE_COMBINED,
+ 'extra' => [],
'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.implement("test.private@{blankVer}",null,{"css":[]});});</script>',
],
[
// Eg. startup module
'modules' => [ 'test.scripts.raw' ],
'only' => ResourceLoaderModule::TYPE_SCRIPTS,
- 'output' => '<script async="" src="/w/load.php?debug=false&lang=nl&modules=test.scripts.raw&only=scripts&skin=fallback"></script>',
+ 'extra' => [],
+ 'output' => '<script async="" src="/w/load.php?lang=nl&modules=test.scripts.raw&only=scripts&skin=fallback"></script>',
],
[
- 'context' => [ 'sync' => true ],
+ 'context' => [],
'modules' => [ 'test.scripts.raw' ],
'only' => ResourceLoaderModule::TYPE_SCRIPTS,
- 'output' => '<script src="/w/load.php?debug=false&lang=nl&modules=test.scripts.raw&only=scripts&skin=fallback&sync=1"></script>',
+ 'extra' => [ 'sync' => '1' ],
+ 'output' => '<script src="/w/load.php?lang=nl&modules=test.scripts.raw&only=scripts&skin=fallback&sync=1"></script>',
],
[
'context' => [],
'modules' => [ 'test.scripts.user' ],
'only' => ResourceLoaderModule::TYPE_SCRIPTS,
- 'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?debug=false\u0026lang=nl\u0026modules=test.scripts.user\u0026only=scripts\u0026skin=fallback\u0026user=Example\u0026version=0a56zyi");});</script>',
+ 'extra' => [],
+ 'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test.scripts.user\u0026only=scripts\u0026skin=fallback\u0026user=Example\u0026version=0a56zyi");});</script>',
],
[
'context' => [],
'modules' => [ 'test.user' ],
'only' => ResourceLoaderModule::TYPE_COMBINED,
- 'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?debug=false\u0026lang=nl\u0026modules=test.user\u0026skin=fallback\u0026user=Example\u0026version=0a56zyi");});</script>',
+ 'extra' => [],
+ 'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test.user\u0026skin=fallback\u0026user=Example\u0026version=0a56zyi");});</script>',
],
[
- 'context' => [ 'debug' => true ],
+ 'context' => [ 'debug' => 'true' ],
'modules' => [ 'test.styles.pure', 'test.styles.mixed' ],
'only' => ResourceLoaderModule::TYPE_STYLES,
+ 'extra' => [],
'output' => '<link rel="stylesheet" href="/w/load.php?debug=true&lang=nl&modules=test.styles.mixed&only=styles&skin=fallback"/>' . "\n"
. '<link rel="stylesheet" href="/w/load.php?debug=true&lang=nl&modules=test.styles.pure&only=styles&skin=fallback"/>',
],
[
- 'context' => [ 'debug' => false ],
+ 'context' => [ 'debug' => 'false' ],
'modules' => [ 'test.styles.pure', 'test.styles.mixed' ],
'only' => ResourceLoaderModule::TYPE_STYLES,
- 'output' => '<link rel="stylesheet" href="/w/load.php?debug=false&lang=nl&modules=test.styles.mixed%2Cpure&only=styles&skin=fallback"/>',
+ 'extra' => [],
+ 'output' => '<link rel="stylesheet" href="/w/load.php?lang=nl&modules=test.styles.mixed%2Cpure&only=styles&skin=fallback"/>',
],
[
'context' => [],
'modules' => [ 'test.styles.noscript' ],
'only' => ResourceLoaderModule::TYPE_STYLES,
- 'output' => '<noscript><link rel="stylesheet" href="/w/load.php?debug=false&lang=nl&modules=test.styles.noscript&only=styles&skin=fallback"/></noscript>',
+ 'extra' => [],
+ 'output' => '<noscript><link rel="stylesheet" href="/w/load.php?lang=nl&modules=test.styles.noscript&only=styles&skin=fallback"/></noscript>',
],
[
'context' => [],
'modules' => [ 'test.shouldembed' ],
'only' => ResourceLoaderModule::TYPE_COMBINED,
+ 'extra' => [],
'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.implement("test.shouldembed@09p30q0",null,{"css":[]});});</script>',
],
[
'context' => [],
'modules' => [ 'test.styles.shouldembed' ],
'only' => ResourceLoaderModule::TYPE_STYLES,
+ 'extra' => [],
'output' => '<style>.shouldembed{}</style>',
],
[
'context' => [],
'modules' => [ 'test.scripts.shouldembed' ],
'only' => ResourceLoaderModule::TYPE_SCRIPTS,
+ 'extra' => [],
'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.state({"test.scripts.shouldembed":"ready"});});</script>',
],
[
'context' => [],
'modules' => [ 'test', 'test.shouldembed' ],
'only' => ResourceLoaderModule::TYPE_COMBINED,
- 'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?debug=false\u0026lang=nl\u0026modules=test\u0026skin=fallback");mw.loader.implement("test.shouldembed@09p30q0",null,{"css":[]});});</script>',
+ 'extra' => [],
+ 'output' => '<script>(window.RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test\u0026skin=fallback");mw.loader.implement("test.shouldembed@09p30q0",null,{"css":[]});});</script>',
],
[
'context' => [],
'modules' => [ 'test.styles.pure', 'test.styles.shouldembed' ],
'only' => ResourceLoaderModule::TYPE_STYLES,
+ 'extra' => [],
'output' =>
- '<link rel="stylesheet" href="/w/load.php?debug=false&lang=nl&modules=test.styles.pure&only=styles&skin=fallback"/>' . "\n"
+ '<link rel="stylesheet" href="/w/load.php?lang=nl&modules=test.styles.pure&only=styles&skin=fallback"/>' . "\n"
. '<style>.shouldembed{}</style>'
],
[
'context' => [],
'modules' => [ 'test.ordering.a', 'test.ordering.e', 'test.ordering.b', 'test.ordering.d', 'test.ordering.c' ],
'only' => ResourceLoaderModule::TYPE_STYLES,
+ 'extra' => [],
'output' =>
- '<link rel="stylesheet" href="/w/load.php?debug=false&lang=nl&modules=test.ordering.a%2Cb&only=styles&skin=fallback"/>' . "\n"
+ '<link rel="stylesheet" href="/w/load.php?lang=nl&modules=test.ordering.a%2Cb&only=styles&skin=fallback"/>' . "\n"
. '<style>.orderingC{}.orderingD{}</style>' . "\n"
- . '<link rel="stylesheet" href="/w/load.php?debug=false&lang=nl&modules=test.ordering.e&only=styles&skin=fallback"/>'
+ . '<link rel="stylesheet" href="/w/load.php?lang=nl&modules=test.ordering.e&only=styles&skin=fallback"/>'
],
];
// phpcs:enable
* @covers ResourceLoader::makeLoaderQuery
* @covers ResourceLoader::makeInlineScript
*/
- public function testMakeLoad( array $extraQuery, array $modules, $type, $expected ) {
- $context = self::makeContext( $extraQuery );
+ public function testMakeLoad(
+ array $contextQuery,
+ array $modules,
+ $type,
+ array $extraQuery,
+ $expected
+ ) {
+ $context = self::makeContext( $contextQuery );
$context->getResourceLoader()->register( self::makeSampleModules() );
$actual = ResourceLoaderClientHtml::makeLoad( $context, $modules, $type, $extraQuery, false );
$expected = self::expandVariables( $expected );
}, $scripts );
$rl->register( $modules );
- $this->setMwGlobals( 'wgResourceLoaderDebug', $debug );
$context = $this->getResourceLoaderContext(
[
'modules' => implode( '|', array_keys( $modules ) ),
'only' => 'scripts',
+ 'debug' => $debug ? 'true' : 'false',
],
$rl
);